package com.dycong.common.http.SSLEngineUtil;

import javax.net.ssl.*;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Iterator;

/**
 * Created by dycong on 2017/4/3.
 */
public class NioSslServer extends SSLEnginePeer {

    private boolean active;
    private SSLContext sslContext;
    private Selector selector;
    private SSLEngine sslEngine;

    private String kFilepath = "F:/MyUtil/src/main/resources/server.jks";
    String keystorePassword = "storepass";
    String keyPassword = "keypass";
    private String tFilepath = "F:/MyUtil/src/main/resources/trustedCerts.jks";
    String trustStorePassword = "storepass";

    /**
     * @param protocol    - the SSL/TLS protocol that this server will be configured to apply.
     * @param hostAddress
     * @param port
     */
    public NioSslServer(String protocol, String hostAddress, int port) {
        init(protocol, hostAddress, port);
    }

    private void init(String protocol, String hostAddress, int port) {
        try {
            sslContext = SSLContext.getInstance(protocol);
            sslContext.init(createKeyManagers(kFilepath, keystorePassword, keyPassword), createTrustManagers(tFilepath, trustStorePassword), new SecureRandom());
            sslEngine = sslContext.createSSLEngine();
            sslEngine.setUseClientMode(false);
            SSLSession sslSession = sslEngine.getSession();
            //???
            sslSession.invalidate();
            selector = Selector.open();
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(hostAddress, port));
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            active = true;
        } catch (NoSuchAlgorithmException e) {
            log.error("" + e);
        } catch (KeyManagementException e) {
            log.error("sslContext init error: " + e);
        } catch (IOException e) {
            log.error("select init error: " + e);
        }
    }

    public void start() {
        log.debug("Initialized and waiting for new connections...");
        while (active) {
            try {
                //// TODO: 2017/4/4 This method performs a blocking <a href="#selop">selection* operation
                int c = selector.select();
                if (c == 0) continue;
                Iterator<SelectionKey> it = selector.selectedKeys().iterator();
                while (it.hasNext()) {
                    SelectionKey key = it.next();
                    it.remove();
                    if (!key.isValid()) continue;
                    ;
                    if (key.isAcceptable()) {
                        accept(key);
                    }
                    if (key.isReadable()) {
                        SocketChannel channel=(SocketChannel)key.channel();
                        read(channel,sslEngine);
                    }
                }
            } catch (IOException e) {
                log.error(e);
            }
        }
    }

    public void stop() {
        log.error("close server now...");
        active = false;
        sslExecutor.shutdown();
        try {
            selector.close();
        } catch (IOException e) {
            log.error("selector close error: " + e);
        }
    }

    private void accept(SelectionKey key) {
        log.debug("New connection request!");
        try {
            ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
            SocketChannel channel = (SocketChannel) serverChannel.accept();
            if (channel != null) {
                if (channel.finishConnect()) {
                    //todo 每一次都要创建一个sslEngine!!!--->>>fix花费2小时
                    sslEngine = sslContext.createSSLEngine();
                    sslEngine.setUseClientMode(false);
                    SSLSession sslSession = sslEngine.getSession();
                    //???
                    sslSession.invalidate();
                    sslEngine.beginHandshake();
                    // TODO: 2017/4/4 提前设为non-blocking否则握手是可能会阻塞 
                    channel.configureBlocking(false);
                    if (handShake(channel, sslEngine)) {
                        channel.register(selector, SelectionKey.OP_READ);
                    } else {
                        log.warn("Connection closed due to handshake failure.");
                    }
                }
            }
        } catch (IOException e) {
            log.error("" + e);
        }
    }

    @Override
    protected void read(SocketChannel socketChannel, SSLEngine engine) {
        inNetBuffer.clear();
        try {
            int read = socketChannel.read(inNetBuffer);
            if (read > 0) {
                inNetBuffer.flip();
                inAppBuffer.clear();
                SSLEngineResult result = sslEngine.unwrap(inNetBuffer, inAppBuffer);
                switch (result.getStatus()) {
                    case OK:
                        log.info("server receive: " + new String(inAppBuffer.array()));
                        break;
                    case BUFFER_OVERFLOW:
                        inAppBuffer = enlargeApplicationBuffer(sslEngine, inAppBuffer);
                        break;
                    case BUFFER_UNDERFLOW:
                        inNetBuffer = handleBufferUnderflow(sslEngine, inNetBuffer);
                    case CLOSED:
                        log.debug("Client wants to close connection...");
                        closeConnection(socketChannel, engine);
                        log.debug("Goodbye client!");
                        return;
                    default:
                        throw new RuntimeException();
                }
                write(socketChannel, engine, "Hello! I am your server!");
            } else if (read < 0) {
                log.error("Received end of stream. Will try to close connection with client...");
                handleEndOfStream(socketChannel, sslEngine);
            }

        } catch (IOException e) {

        }

    }

    @Override
    protected void write(SocketChannel socketChannel, SSLEngine engine, ByteBuffer message) {
        outNetBuffer.clear();
        outAppBuffer.clear();
        message.flip();
        outAppBuffer.put(message);
        outAppBuffer.flip();
        try {
            outNetBuffer.clear();
            SSLEngineResult result = sslEngine.wrap(outAppBuffer, outNetBuffer);
            switch (result.getStatus()) {
                case OK:
                    outNetBuffer.flip();
                    while (outNetBuffer.hasRemaining())
                        socketChannel.write(outNetBuffer);
                    break;
                case BUFFER_OVERFLOW:
                    outAppBuffer = enlargePacketBuffer(sslEngine, outAppBuffer);
                    break;
                case BUFFER_UNDERFLOW:
                    throw new SSLException("Buffer underflow occured after a wrap. I don't think we should ever get here.");
                case CLOSED:
                    closeConnection(socketChannel, sslEngine);
                    return;
                default:
                    throw new RuntimeException();
            }

        } catch (SSLException e) {

        } catch (IOException e) {

        }
    }


    private boolean isActive() {
        return active;
    }

}
